package org.kiwi.data.interceptor;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.kiwi.data.TableShardingStrategyHolder;
import org.kiwi.data.annotation.TableSharding;
import org.kiwi.data.sharding.TableShardingStrategy;
import org.kiwi.data.util.MethodUtil;
import org.kiwi.data.util.SPELUtil;
import org.kiwi.data.vo.TableShardingParameter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;

import java.lang.reflect.Method;
import java.util.Map;

/**
 * Created by jack on 16/12/22.
 */
public class TableShardingInterceptor implements MethodInterceptor {

    private static final Logger LOGGER = LoggerFactory.getLogger(TableShardingInterceptor.class);

    @Autowired
    private ApplicationContext applicationContext;

    private ParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();

    private Map<String, TableShardingStrategy> tableShardingStrategies;

    public void setTableShardingStrategies(Map<String, TableShardingStrategy> tableShardingStrategies) {
        this.tableShardingStrategies = tableShardingStrategies;
    }

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        String debugInfo = "[" + invocation.toString() + "]";

        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("enter into table sharding interceptor {}", debugInfo);
        }

        Method realMethod = MethodUtil.getRealMethod(invocation);
        TableSharding tableShardingAnnotation = realMethod.getAnnotation(TableSharding.class);

        if (tableShardingAnnotation != null) {

            Object shardingParameter = SPELUtil.getValue(
                    this.parameterNameDiscoverer.getParameterNames(realMethod),
                    invocation.getArguments(),
                    tableShardingAnnotation.expression(),
                    this.applicationContext);

            TableShardingStrategy tableShardingStrategy = this.tableShardingStrategies.get(tableShardingAnnotation.strategyName());
            if (tableShardingStrategy != null) {
                TableShardingStrategyHolder.setTableShardingParameter(
                        new TableShardingParameter(tableShardingAnnotation.strategyName(), shardingParameter, tableShardingStrategy)
                );
            } else {
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("table sharding strategy is null {}", debugInfo);
                }
            }

        } else {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("table sharding annotation is null {}", debugInfo);
            }
        }

        try {
            return invocation.proceed();
        } catch (Exception e) {
            LOGGER.error("invoke table sharding interceptor has some problem.", e);
            throw e;
        } finally {
            TableShardingStrategyHolder.clear();

            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("leave from table sharding interceptor {}", debugInfo);
            }
        }
    }

}
